package org.groovymud.shell.telnetd;

/* Copyright 2008 Matthew Corby-Eaglen
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. 
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0 
 *
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */
import groovy.util.ResourceException;
import groovy.util.ScriptException;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.ConfirmationCallback;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

import net.wimpi.telnetd.net.Connection;
import net.wimpi.telnetd.net.ConnectionEvent;
import net.wimpi.telnetd.shell.Shell;

import org.apache.log4j.Logger;
import org.codehaus.groovy.control.CompilationFailedException;
import org.groovymud.object.alive.Player;
import org.groovymud.object.registry.MudObjectAttendant;
import org.groovymud.shell.security.ActionCallback;

/* specific instance of a player connection */

public class LoginShell extends ShellBridge implements CallbackHandler {

    private static final String BEAN_NAME = "loginShell";

    private final static Logger logger = Logger.getLogger(LoginShell.class);

    private String mudSplashPageLocation;
    private int loginAttempts;
    private MudObjectAttendant objectLoader;

    Player player = null;

    public void run(Connection connection) {

        setConnection(connection);
        setTerminalIO(createTerminalIO(connection));
        connection.addConnectionListener(this);

        // clear the screen and start from zero
        ExtendedTerminalIO io = getTerminalIO();

        LoginContext lc = null;
        try {
            io.eraseScreen();
            io.homeCursor();

            String splashPage = loadMudSplash();
            io.write(splashPage);
            io.write("");

            try {
                lc = new LoginContext("Mud", this);
            } catch (SecurityException e) {
                logger.error(e, e);
            } catch (LoginException e) {
                // TODO Auto-generated catch block
                logger.error(e, e);
            }

            int logins = 0;
            for (; logins < 3; logins++) {
                try {
                    lc.login();
                    break;
                } catch (LoginException e) {
                    // TODO Auto-generated catch block
                    logger.error(e, e);

                }
            }

            if (logins < 3) {
                if (player != null) {
                    getObjectRegistry().addActivePlayer(this, player);
                    player.setTerminalOutput(io);
                    doMovePlayer(io, player);
                    getConnection().getConnectionData().getEnvironment().put("player", player);
                    getConnection().getConnectionData().getEnvironment().put("registry", getObjectRegistry());
                    getConnection().getConnectionData().getEnvironment().put("loginContext", lc);

                    getConnection().setNextShell(MudShell.BEAN_NAME);
                    getConnection().removeConnectionListener(this);
                }
            } else {
                io.writeln("Sorry too many login attempts.");
            }

        } catch (IOException e) {
            logger.error(e, e);
        }
    }

    protected ExtendedTerminalIO createTerminalIO(Connection connection) {
        return new ExtendedTerminalIO(connection);
    }

    private void doMovePlayer(ExtendedTerminalIO io, Player player) throws FileNotFoundException, IOException {
        boolean error = false;
        try {
            getObjectLoader().movePlayerToLocation(player);
        } catch (CompilationFailedException e) {
            // TODO Auto-generated catch block
            error = true;
            logger.error(e, e);
        } catch (InstantiationException e) {
            // TODO Auto-generated catch block
            logger.error(e, e);
        } finally {
            if (error) {
                io.writeln("Aarrrrggh you've fallen into the void!!!1! Quick! Get a cre!!");
                getObjectLoader().movePlayerToVoid(player);
            }
        }
    }

    protected String requestUsername(NameCallback nc) throws IOException {
        ExtendedTerminalIO io = getTerminalIO();
        io.write(nc.getPrompt());
        io.flush();
        String username = io.readln(false);

        return username;
    }

    protected Player doRegisterNewPlayer(String username) {
        try {
            return getObjectLoader().createNewPlayer(username);
        } catch (CompilationFailedException e) {
            logger.error(e, e);
        } catch (FileNotFoundException e) {
            logger.error(e, e);
        } catch (InstantiationException e) {
            logger.error(e, e);
        }
        return null;
    }

    protected String retrievePassword(PasswordCallback pc) throws IOException {
        ExtendedTerminalIO io = getTerminalIO();
        io.write(pc.getPrompt());
        io.flush();
        return io.readln(pc.isEchoOn());
    }

    private Player getLoggedInPlayer(String loggingIn) {
        return (Player) getObjectRegistry().getMudObject(loggingIn);
    }

    protected Player loadPlayer(String username) throws CompilationFailedException, FileNotFoundException, ResourceException, ScriptException, InstantiationException {
        return getObjectLoader().loadPlayerData(username);
    }

    public Player tryToloadPlayer(String username) {
        Player player = null;
        try {
            player = loadPlayer(username);
        } catch (CompilationFailedException e) {
            logger.error(e, e);
        } catch (InstantiationException e) {
            logger.error(e, e);
        } catch (ResourceException e) {
            logger.error(e, e);
        } catch (ScriptException e) {
            logger.error(e, e);
        } catch (FileNotFoundException e) {
            logger.error(e, e);
        }
        return player;
    }

    protected String loadMudSplash() {
        InputStream in;
        BufferedReader bIn;
        try {
            in = new FileInputStream(new File(mudSplashPageLocation));

            bIn = new BufferedReader(new InputStreamReader(in));

            StringBuffer buffer = new StringBuffer();
            String str = null;
            while ((str = bIn.readLine()) != null) {
                buffer.append(str + "\n");
            }
            return buffer.toString();

        } catch (IOException e) {
            logger.error(e, e);
        }
        return null;
    }

    public static Shell createShell() {
        return (Shell) getContext().getBean(BEAN_NAME);
    }

    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
        String username = null;

        for (int x = 0; x < callbacks.length; x++) {
            if (callbacks[x] instanceof NameCallback) {
                NameCallback nc = (NameCallback) callbacks[x];
                username = requestUsername(nc);
                nc.setName(username);
            } else if (callbacks[x] instanceof PasswordCallback) {
                PasswordCallback pc = (PasswordCallback) callbacks[x];
                pc.setPassword(retrievePassword(pc).toCharArray());
            } else if (callbacks[x] instanceof ConfirmationCallback) {
                ConfirmationCallback conf = (ConfirmationCallback) callbacks[x];
                if (conf.getMessageType() != ConfirmationCallback.YES_NO_OPTION) {
                    throw new UnsupportedCallbackException(conf, "cannot handle none YN questions yet");
                }
                confirmNewPlayer(conf);
            } else if (callbacks[x] instanceof ActionCallback) {
                ActionCallback cp = (ActionCallback) callbacks[x];
                if (cp.getAction().equals(ActionCallback.LOAD_ACTION)) {
                    this.setPlayer(tryToloadPlayer(cp.getObjectName()));
                    cp.setMudObject(player);
                }
                if (cp.getAction().equals(ActionCallback.CREATE_ACTION)) {
                    this.setPlayer(this.doRegisterNewPlayer(cp.getObjectName()));
                    cp.setMudObject(player);
                }
                if (cp.getAction().equals(ActionCallback.EXISTS_ACTION)) {
                    cp.setMudObject(getLoggedInPlayer(cp.getObjectName()));
                }
            } else {
                throw new UnsupportedCallbackException(callbacks[x]);
            }
        }

    }

    protected void confirmNewPlayer(ConfirmationCallback conf) throws IOException {
        ExtendedTerminalIO io = getTerminalIO();

        io.writeln(conf.getPrompt());
        String response = io.readln(false);

        if (response.substring(0, 1).equalsIgnoreCase("y")) {
            conf.setSelectedIndex(ConfirmationCallback.YES);
        } else {
            conf.setSelectedIndex(ConfirmationCallback.NO);
        }

    }

    public int getLoginAttempts() {
        return loginAttempts;
    }

    public void setLoginAttempts(int loginAttempts) {
        this.loginAttempts = loginAttempts;
    }

    public String getMudSplashPageLocation() {
        return mudSplashPageLocation;
    }

    public void setMudSplashPageLocation(String mudSplashPageLocation) {
        this.mudSplashPageLocation = mudSplashPageLocation;
    }

    public MudObjectAttendant getObjectLoader() {
        return objectLoader;
    }

    public void setObjectLoader(MudObjectAttendant objectLoader) {
        this.objectLoader = objectLoader;
    }

    // this implements the ConnectionListener!
    public void connectionTimedOut(ConnectionEvent ce) {
        try {
            getTerminalIO().write("CONNECTION_TIMEDOUT");
            getTerminalIO().flush();
            // close connection
            getConnection().close();
        } catch (Exception ex) {
            logger.error("connectionTimedOut()", ex);
        }
    }// connectionTimedOut

    public void connectionIdle(ConnectionEvent ce) {
        try {
            getTerminalIO().write("CONNECTION_IDLE");

            getTerminalIO().flush();
        } catch (IOException e) {
            logger.error(e, e);
        }
    }// connectionIdle

    public void connectionLogoutRequest(ConnectionEvent ce) {
        try {
            getTerminalIO().write("CONNECTION_LOGOUTREQUEST");
            getTerminalIO().flush();
        } catch (IOException e) {
            logger.error(e, e);
        }

    }// connectionLogout

    public void connectionSentBreak(ConnectionEvent ce) {
        try {
            getTerminalIO().write("CONNECTION_BREAK");
            getTerminalIO().flush();
        } catch (IOException e) {
            logger.error(e, e);
        }

    }

    public Player getPlayer() {
        return player;
    }

    public void setPlayer(Player player) {
        this.player = player;
    }

}
